//
// SmoothScroll for websites v1.2.1
// Licensed under the terms of the MIT license.
//
// You may use it in your theme if you credit me. 
// It is also free to use on any individual website.
//
// Exception:
// The only restriction would be not to publish any  
// extension for browsers or native application
// without getting a permission first.
//

// People involved
//  - Balazs Galambosi (maintainer)   
//  - Michael Herf     (Pulse Algorithm)

(function($) {
    $.extend({
        webkitSmoothScroll: function(){
              
            // Scroll Variables (tweakable)
            var defaultOptions = {

                // Scrolling Core
                frameRate        : 120, // [Hz]
                animationTime    : 600, // [px]
                stepSize         : 135, // [px]

                // Pulse (less tweakable)
                // ratio of "tail" to "acceleration"
                pulseAlgorithm   : true,
                pulseScale       : 7,
                pulseNormalize   : 1,

                // Acceleration
                accelerationDelta : 20,  // 20
                accelerationMax   : 1,   // 1

                // Keyboard Settings
                keyboardSupport   : true,  // option
                arrowScroll       : 50,     // [px]

                // Other
                touchpadSupport   : true,
                fixedBackground   : true, 
                excluded          : ""    
            };

            var options = defaultOptions;


            // Other Variables
            var isExcluded = false;
            var isFrame = false;
            var direction = { x: 0, y: 0 };
            var initDone  = false;
            var root = document.documentElement;
            var activeElement;
            var observer;
            var deltaBuffer = [ 120, 120, 120 ];

            var key = { left: 37, up: 38, right: 39, down: 40, spacebar: 32, 
                        pageup: 33, pagedown: 34, end: 35, home: 36 };


            /***********************************************
             * SETTINGS
             ***********************************************/

            var options = defaultOptions;


            /***********************************************
             * INITIALIZE
             ***********************************************/

            /**
             * Tests if smooth scrolling is allowed. Shuts down everything if not.
             */
            function initTest() {

                var disableKeyboard = false; 
                
                // disable keyboard support if anything above requested it
                if (disableKeyboard) {
                    removeEvent("keydown", keydown);
                }

                if (options.keyboardSupport && !disableKeyboard) {
                    addEvent("keydown", keydown);
                }
            }

            /**
             * Sets up scrolls array, determines if frames are involved.
             */
            function init() {
              
                if (!document.body) return;

                var body = document.body;
                var html = document.documentElement;
                var windowHeight = window.innerHeight; 
                var scrollHeight = body.scrollHeight;
                
                // check compat mode for root element
                root = (document.compatMode.indexOf('CSS') >= 0) ? html : body;
                activeElement = body;
                
                initTest();
                initDone = true;

                // Checks if this script is running in a frame
                if (top != self) {
                    isFrame = true;
                }

                /**
                 * This fixes a bug where the areas left and right to 
                 * the content does not trigger the onmousewheel event
                 * on some pages. e.g.: html, body { height: 100% }
                 */
                else if (scrollHeight > windowHeight &&
                        (body.offsetHeight <= windowHeight || 
                         html.offsetHeight <= windowHeight)) {

                    // DOMChange (throttle): fix height
                    var pending = false;
                    var refresh = function () {
                        if (!pending && html.scrollHeight != document.height) {
                            pending = true; // add a new pending action
                            setTimeout(function () {
                                html.style.height = document.height + 'px';
                                pending = false;
                            }, 500); // act rarely to stay fast
                        }
                    };
                    html.style.height = 'auto';
                    setTimeout(refresh, 10);

                    // clearfix
                    if (root.offsetHeight <= windowHeight) {
                        var underlay = document.createElement("div"); 	
                        underlay.style.clear = "both";
                        body.appendChild(underlay);
                    }
                }

                // disable fixed background
                if (!options.fixedBackground && !isExcluded) {
                    body.style.backgroundAttachment = "scroll";
                    html.style.backgroundAttachment = "scroll";
                }
            }


            /************************************************
             * SCROLLING 
             ************************************************/
             
            var que = [];
            var pending = false;
            var lastScroll = +new Date;

            /**
             * Pushes scroll actions to the scrolling queue.
             */
            function scrollArray(elem, left, top, delay) {
                
                delay || (delay = 1000);
                directionCheck(left, top);

                if (options.accelerationMax != 1) {
                    var now = +new Date;
                    var elapsed = now - lastScroll;
                    if (elapsed < options.accelerationDelta) {
                        var factor = (1 + (30 / elapsed)) / 2;
                        if (factor > 1) {
                            factor = Math.min(factor, options.accelerationMax);
                            left *= factor;
                            top  *= factor;
                        }
                    }
                    lastScroll = +new Date;
                }          
                
                // push a scroll command
                que.push({
                    x: left, 
                    y: top, 
                    lastX: (left < 0) ? 0.99 : -0.99,
                    lastY: (top  < 0) ? 0.99 : -0.99, 
                    start: +new Date
                });
                    
                // don't act if there's a pending queue
                if (pending) {
                    return;
                }  

                var scrollWindow = (elem === document.body);
                
                var step = function (time) {
                    
                    var now = +new Date;
                    var scrollX = 0;
                    var scrollY = 0; 
                
                    for (var i = 0; i < que.length; i++) {
                        
                        var item = que[i];
                        var elapsed  = now - item.start;
                        var finished = (elapsed >= options.animationTime);
                        
                        // scroll position: [0, 1]
                        var position = (finished) ? 1 : elapsed / options.animationTime;
                        
                        // easing [optional]
                        if (options.pulseAlgorithm) {
                            position = pulse(position);
                        }
                        
                        // only need the difference
                        var x = (item.x * position - item.lastX) >> 0;
                        var y = (item.y * position - item.lastY) >> 0;
                        
                        // add this to the total scrolling
                        scrollX += x;
                        scrollY += y;            
                        
                        // update last values
                        item.lastX += x;
                        item.lastY += y;
                    
                        // delete and step back if it's over
                        if (finished) {
                            que.splice(i, 1); i--;
                        }           
                    }

                    // scroll left and top
                    if (scrollWindow) {
                        window.scrollBy(scrollX, scrollY);
                    } 
                    else {
                        if (scrollX) elem.scrollLeft += scrollX;
                        if (scrollY) elem.scrollTop  += scrollY;                    
                    }
                    
                    // clean up if there's nothing left to do
                    if (!left && !top) {
                        que = [];
                    }
                    
                    if (que.length) { 
                        requestFrame(step, elem, (delay / options.frameRate + 1)); 
                    } else { 
                        pending = false;
                    }
                };
                
                // start a new queue of actions
                requestFrame(step, elem, 0);
                pending = true;
            }


            /***********************************************
             * EVENTS
             ***********************************************/

            /**
             * Mouse wheel handler.
             * @param {Object} event
             */
            function wheel(event) {

                if (!initDone) {
                    init();
                }
                
                var target = event.target;
                var overflowing = overflowingAncestor(target);
                
                // use default if there's no overflowing
                // element or default action is prevented    
                if (!overflowing || event.defaultPrevented ||
                    isNodeName(activeElement, "embed") ||
                   (isNodeName(target, "embed") && /\.pdf/i.test(target.src))) {
                    return true;
                }

                var deltaX = event.wheelDeltaX || 0;
                var deltaY = event.wheelDeltaY || 0;
                
                // use wheelDelta if deltaX/Y is not available
                if (!deltaX && !deltaY) {
                    deltaY = event.wheelDelta || 0;
                }

                // check if it's a touchpad scroll that should be ignored
                if (!options.touchpadSupport && isTouchpad(deltaY)) {
                    return true;
                }

                // scale by step size
                // delta is 120 most of the time
                // synaptics seems to send 1 sometimes
                if (Math.abs(deltaX) > 1.2) {
                    deltaX *= options.stepSize / 120;
                }
                if (Math.abs(deltaY) > 1.2) {
                    deltaY *= options.stepSize / 120;
                }
                
                scrollArray(overflowing, -deltaX, -deltaY);
                event.preventDefault();
            }

            /**
             * Keydown event handler.
             * @param {Object} event
             */
            function keydown(event) {

                var target   = event.target;
                var modifier = event.ctrlKey || event.altKey || event.metaKey || 
                              (event.shiftKey && event.keyCode !== key.spacebar);
                
                // do nothing if user is editing text
                // or using a modifier key (except shift)
                // or in a dropdown
                if ( /input|textarea|select|embed/i.test(target.nodeName) ||
                     target.isContentEditable || 
                     event.defaultPrevented   ||
                     modifier ) {
                  return true;
                }
                // spacebar should trigger button press
                if (isNodeName(target, "button") &&
                    event.keyCode === key.spacebar) {
                  return true;
                }
                
                var shift, x = 0, y = 0;
                var elem = overflowingAncestor(activeElement);
                var clientHeight = elem.clientHeight;

                if (elem == document.body) {
                    clientHeight = window.innerHeight;
                }

                switch (event.keyCode) {
                    case key.up:
                        y = -options.arrowScroll;
                        break;
                    case key.down:
                        y = options.arrowScroll;
                        break;         
                    case key.spacebar: // (+ shift)
                        shift = event.shiftKey ? 1 : -1;
                        y = -shift * clientHeight * 0.9;
                        break;
                    case key.pageup:
                        y = -clientHeight * 0.9;
                        break;
                    case key.pagedown:
                        y = clientHeight * 0.9;
                        break;
                    case key.home:
                        y = -elem.scrollTop;
                        break;
                    case key.end:
                        var damt = elem.scrollHeight - elem.scrollTop - clientHeight;
                        y = (damt > 0) ? damt+10 : 0;
                        break;
                    case key.left:
                        x = -options.arrowScroll;
                        break;
                    case key.right:
                        x = options.arrowScroll;
                        break;            
                    default:
                        return true; // a key we don't care about
                }

                scrollArray(elem, x, y);
                event.preventDefault();
            }

            /**
             * Mousedown event only for updating activeElement
             */
            function mousedown(event) {
                activeElement = event.target;
            }


            /***********************************************
             * OVERFLOW
             ***********************************************/
             
            var cache = {}; // cleared out every once in while
            setInterval(function () { cache = {}; }, 10 * 1000);

            var uniqueID = (function () {
                var i = 0;
                return function (el) {
                    return el.uniqueID || (el.uniqueID = i++);
                };
            })();

            function setCache(elems, overflowing) {
                for (var i = elems.length; i--;)
                    cache[uniqueID(elems[i])] = overflowing;
                return overflowing;
            }

            function overflowingAncestor(el) {
                var elems = [];
                var rootScrollHeight = root.scrollHeight;
                do {
                    var cached = cache[uniqueID(el)];
                    if (cached) {
                        return setCache(elems, cached);
                    }
                    elems.push(el);
                    if (rootScrollHeight === el.scrollHeight) {
                        if (!isFrame || root.clientHeight + 10 < rootScrollHeight) {
                            return setCache(elems, document.body); // scrolling root in WebKit
                        }
                    } else if (el.clientHeight + 10 < el.scrollHeight) {
                        overflow = getComputedStyle(el, "").getPropertyValue("overflow-y");
                        if (overflow === "scroll" || overflow === "auto") {
                            return setCache(elems, el);
                        }
                    }
                } while (el = el.parentNode);
            }


            /***********************************************
             * HELPERS
             ***********************************************/

            function addEvent(type, fn, bubble) {
                window.addEventListener(type, fn, (bubble||false));
            }

            function removeEvent(type, fn, bubble) {
                window.removeEventListener(type, fn, (bubble||false));  
            }

            function isNodeName(el, tag) {
                return (el.nodeName||"").toLowerCase() === tag.toLowerCase();
            }

            function directionCheck(x, y) {
                x = (x > 0) ? 1 : -1;
                y = (y > 0) ? 1 : -1;
                if (direction.x !== x || direction.y !== y) {
                    direction.x = x;
                    direction.y = y;
                    que = [];
                    lastScroll = 0;
                }
            }

            var deltaBufferTimer;

            function isTouchpad(deltaY) {
                if (!deltaY) return;
                deltaY = Math.abs(deltaY)
                deltaBuffer.push(deltaY);
                deltaBuffer.shift();
                clearTimeout(deltaBufferTimer);
                var allDivisable = (isDivisible(deltaBuffer[0], 120) &&
                                    isDivisible(deltaBuffer[1], 120) &&
                                    isDivisible(deltaBuffer[2], 120));
                return !allDivisable;
            } 

            function isDivisible(n, divisor) {
                return (Math.floor(n / divisor) == n / divisor);
            }

            var requestFrame = (function () {
                  return  window.requestAnimationFrame       || 
                          window.webkitRequestAnimationFrame || 
                          function (callback, element, delay) {
                              window.setTimeout(callback, delay || (1000/60));
                          };
            })();


            /***********************************************
             * PULSE
             ***********************************************/
             
            /**
             * Viscous fluid with a pulse for part and decay for the rest.
             * - Applies a fixed force over an interval (a damped acceleration), and
             * - Lets the exponential bleed away the velocity over a longer interval
             * - Michael Herf, http://stereopsis.com/stopping/
             */
            function pulse_(x) {
                var val, start, expx;
                // test
                x = x * options.pulseScale;
                if (x < 1) { // acceleartion
                    val = x - (1 - Math.exp(-x));
                } else {     // tail
                    // the previous animation ended here:
                    start = Math.exp(-1);
                    // simple viscous drag
                    x -= 1;
                    expx = 1 - Math.exp(-x);
                    val = start + (expx * (1 - start));
                }
                return val * options.pulseNormalize;
            }

            function pulse(x) {
                if (x >= 1) return 1;
                if (x <= 0) return 0;

                if (options.pulseNormalize == 1) {
                    options.pulseNormalize /= pulse_(1);
                }
                return pulse_(x);
            }

            var isChrome = /chrome/i.test(window.navigator.userAgent);
            var wheelEvent = null;
            if ("onwheel" in document.createElement("div"))
            	wheelEvent = "wheel";
            else if ("onmousewheel" in document.createElement("div"))
            	wheelEvent = "mousewheel";

            if (wheelEvent && isChrome) {
            	addEvent(wheelEvent, wheel);
            	addEvent("mousedown", mousedown);
            	addEvent("load", init);
            }

        }
    });
})(jQuery);